<!DOCTYPE html>
<html lang="zh-cn">
  <head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta name="author" content="Zhou Wei <zromyk@163.com>">
  <title>FreeRTOS</title>
  <link rel="shortcut icon" href="/favicon.ico">
  <link rel="stylesheet" href="/style/html/pure.css">
  <link rel="stylesheet" href="/style/html/main.css">
  <link rel="stylesheet" href="https://cdn.staticfile.org/font-awesome/4.7.0/css/font-awesome.css">
  <!-- <link rel="stylesheet" href="https://apps.bdimg.com/libs/highlight.js/9.1.0/styles/default.min.css"> -->
<link rel="stylesheet" href="/style/article/highlight/default.min.css">
<link rel="stylesheet" href="/style/article/pell-1.0.6/dist/pell.css">

</head>
<body>
  <div id="menu-background"></div>
  <div id="menu">
    <div class="pure-menu pure-menu-horizontal">
  <ul class="pure-menu-list block-middle">
    <li class="pure-menu-item">
  <a class="pure-menu-heading" href="/index.html">ZROMYK</a>
</li>
<li class="pure-menu-item">
  <a class="pure-menu-link" href="/index.html">主页</a>
</li>
<li class="pure-menu-item">
  <a class="pure-menu-link" href="/public/archive/index.html">归档</a>
</li>
<li class="pure-menu-item">
  <a class="pure-menu-link" href="/public/download/index.html">下载</a>
</li>
<li class="pure-menu-item">
  <a class="pure-menu-link" href="/public/feedback/index.html">反馈</a>
</li>
<li class="pure-menu-item">
  <a class="pure-menu-link" href="/public/about/index.html">关于我</a>
</li>
<li class="pure-menu-item">
  <a class="pure-menu-link" href="https://github.com/zromyk"><i class="fa fa-github" style="font-size:32px"></i></a>
</li>

  </ul>
</div>

  </div>
  <div id="layout">
    <div class="content">
      <div id="nav">
    <div id="navigation" class="navigation">
  <ul class="pure-menu-list">
    <li class="pure-menu-item">
  <a class="pure-menu-link nav2" onclick="animateByNav()" href="#stm32">STM32 用于中断屏蔽的特殊寄存器</a>
</li>
<li class="pure-menu-item">
  <a class="pure-menu-link nav3" onclick="animateByNav()" href="#primask">PRIMASK</a>
</li>
<li class="pure-menu-item">
  <a class="pure-menu-link nav3" onclick="animateByNav()" href="#faultmask">FAULTMASK</a>
</li>
<li class="pure-menu-item">
  <a class="pure-menu-link nav3" onclick="animateByNav()" href="#basepri">BASEPRI</a>
</li>
<li class="pure-menu-item">
  <a class="pure-menu-link nav3" onclick="animateByNav()" href="#freertos">FreeRTOS 开关中断</a>
</li>
<li class="pure-menu-item">
  <a class="pure-menu-link nav3" onclick="animateByNav()" href="#_1">临界段代码</a>
</li>
<li class="pure-menu-item">
  <a class="pure-menu-link nav4" onclick="animateByNav()" href="#_2">提供 任务 级别的 临界区保护</a>
</li>
<li class="pure-menu-item">
  <a class="pure-menu-link nav4" onclick="animateByNav()" href="#_3">提供 中断 级别的 临界区保护</a>
</li>
<li class="pure-menu-item">
  <a class="pure-menu-link nav3" onclick="animateByNav()" href="#_4">任务控制块</a>
</li>
<li class="pure-menu-item">
  <a class="pure-menu-link nav4" onclick="animateByNav()" href="#_5">堆栈大小</a>
</li>
<li class="pure-menu-item">
  <a class="pure-menu-link nav2" onclick="animateByNav()" href="#freertos-api">FreeRTOS 任务相关 API 函数</a>
</li>
<li class="pure-menu-item">
  <a class="pure-menu-link nav3" onclick="animateByNav()" href="#api">任务创建和删除 API 函数</a>
</li>
<li class="pure-menu-item">
  <a class="pure-menu-link nav4" onclick="animateByNav()" href="#xtaskcreate"><span style="overflow-x: auto; max-width:100%; display:inline;"><code>xTaskCreate()</code></span> 动态创建任务</a>
</li>
<li class="pure-menu-item">
  <a class="pure-menu-link nav4" onclick="animateByNav()" href="#xtaskcreatestatic"><span style="overflow-x: auto; max-width:100%; display:inline;"><code>xTaskCreateStatic()</code></span> 静态创建任务</a>
</li>
<li class="pure-menu-item">
  <a class="pure-menu-link nav2" onclick="animateByNav()" href="#freertos_1">FreeRTOS 列表和列表项</a>
</li>
<li class="pure-menu-item">
  <a class="pure-menu-link nav2" onclick="animateByNav()" href="#_6">列表</a>
</li>
<li class="pure-menu-item">
  <a class="pure-menu-link nav2" onclick="animateByNav()" href="#_7">列表项</a>
</li>
<li class="pure-menu-item">
  <a class="pure-menu-link nav2" onclick="animateByNav()" href="#_8">迷你列表项</a>
</li>
<li class="pure-menu-item">
  <a class="pure-menu-link nav2" onclick="animateByNav()" href="#_9">列表和列表项初始化</a>
</li>
<li class="pure-menu-item">
  <a class="pure-menu-link nav3" onclick="animateByNav()" href="#_10">列表初始化</a>
</li>
<li class="pure-menu-item">
  <a class="pure-menu-link nav3" onclick="animateByNav()" href="#_11">列表项初始化</a>
</li>
<li class="pure-menu-item">
  <a class="pure-menu-link nav3" onclick="animateByNav()" href="#_12">列表项插入</a>
</li>
<li class="pure-menu-item">
  <a class="pure-menu-link nav4" onclick="animateByNav()" href="#_13">按列表项的值插入</a>
</li>
<li class="pure-menu-item">
  <a class="pure-menu-link nav4" onclick="animateByNav()" href="#_14">在最后插入</a>
</li>
<li class="pure-menu-item">
  <a class="pure-menu-link nav3" onclick="animateByNav()" href="#_15">列表项删除</a>
</li>
<li class="pure-menu-item">
  <a class="pure-menu-link nav2" onclick="animateByNav()" href="#freertos_2">FreeRTOS 任务切换</a>
</li>
<li class="pure-menu-item">
  <a class="pure-menu-link nav2" onclick="animateByNav()" href="#freertos_3">FreeRTOS 调度器开启和任务相关函数</a>
</li>
<li class="pure-menu-item">
  <a class="pure-menu-link nav3" onclick="animateByNav()" href="#_16">任务调度器开启函数</a>
</li>
<li class="pure-menu-item">
  <a class="pure-menu-link nav3" onclick="animateByNav()" href="#_17">内核相关硬件初始化函数</a>
</li>
<li class="pure-menu-item">
  <a class="pure-menu-link nav4" onclick="animateByNav()" href="#fpu">开启 FPU 函数</a>
</li>
<li class="pure-menu-item">
  <a class="pure-menu-link nav4" onclick="animateByNav()" href="#_18">启动第一个任务</a>
</li>
<li class="pure-menu-item">
  <a class="pure-menu-link nav4" onclick="animateByNav()" href="#svc">SVC 中断服务函数</a>
</li>

  </ul>
</div>

</div>
<div id="content-articles">
  <h1 id="FreeRTOS" class="content-subhead">FreeRTOS</h1>
  <p>
    <span>2021-04-27</span>
    <span><span class="post-category post-category-embedded">嵌入式</span></span>
    <button id="button-markdownEditor" class="pure-button" onclick="markdownEditor()">启用编辑</button>
    <button id="button-save" class="pure-button" onclick="save()">保存</button>
  </p>
  <div id="content-articles-markdownEditor" style="display: none;">
    <h1>编辑 power by pell</h1>
    <div id="editor" class="pell"></div>
    <div style="margin-top:20px;">
        <h3>Text output:</h3>
        <div id="text-output"></div>
    </div>
    <div style="margin-top:20px;">
        <h3>HTML output:</h3>
        <pre id="html-output"></pre>
    </div>
  </div>
  <div id="content-articles-markdown">
    <h2 id="stm32">STM32 用于中断屏蔽的特殊寄存器</h2>
<h3 id="primask">PRIMASK</h3>
<p>禁止除 复位、NMI 和 HardFalut 外的所有异常和中断。</p>
<p>使用汇编指令 CPS（修改处理器状态）修改 PRIMASK 寄存器的数值：</p>
<pre><code class="pre-wrap"><span style="overflow-x: auto; max-width:100%; display:inline;"><code>CPSIE   I;  // 设置 PRIMASK 使能中断
CPSID   I;  // 设置 PRIMASK 禁止中断
</code></span></code></pre>
<p>通过 MRS 和 MSR 指令访问 PRIMASK 寄存器：</p>
<pre><code class="pre-wrap"><span style="overflow-x: auto; max-width:100%; display:inline;"><code>MOVS    R0, #0;
MSR     PRIMASK, R0;    // 将 0 写入 PRIMASK 使能中断

MOVS    R0, #1;
MSR     PRIMASK, R0;    // 将 1 写入 PRIMASK 禁止所有中断
</code></span></code></pre>
<p>UCOS 中临界区代码代码保护就是通过开关中断实现的（UCOSIII 也可以使用禁止任务调度的方法来实现临界区代码保护），开关中断就是直接操作 PRIMASK 寄存器，所以在 UCOS 中关闭中断的时候关闭了除复位、NMI 和 HardFault 以外的所有中断。</p>
<h3 id="faultmask">FAULTMASK</h3>
<p>FAULTMASK 的使用方法和 PRIMASK 类似，但是它会把 HardFault 都屏蔽掉，FAULTMASK 会在退出时自动清零。</p>
<p>使用汇编指令 CPS（修改处理器状态）修改 PRIMASK 寄存器的数值：</p>
<pre><code class="pre-wrap"><span style="overflow-x: auto; max-width:100%; display:inline;"><code>CPSIE   F;  // 设置 FAULTMASK 使能中断
CPSID   F;  // 设置 FAULTMASK 禁止中断
</code></span></code></pre>
<p>通过 MRS 和 MSR 指令访问 FAULTMASK 寄存器：</p>
<pre><code class="pre-wrap"><span style="overflow-x: auto; max-width:100%; display:inline;"><code>MOVS    R0, #0;
MSR     FAULTMASK, R0;  // 将 0 写入 FAULTMASK 使能中断

MOVS    R0, #1;
MSR     FAULTMASK, R0;  // 将 1 写入 FAULTMASK 禁止所有中断
</code></span></code></pre>
<h3 id="basepri">BASEPRI</h3>
<p>BASEPRI 寄存器能够屏蔽 <strong>优先级 数值 大于等于 n 的中断和异常</strong>。（Cortex-M 中数值越大优先级越低）</p>
<p>比如我们要屏蔽优先级不高于 0x60 的中断（实际中有测试只比较抢占优先级，非抢占优先级即子优先级不参与计算）：</p>
<pre><code class="pre-wrap"><span style="overflow-x: auto; max-width:100%; display:inline;"><code>MOV     R0, #0x60;
MSR     BASEPRI, R0;
</code></span></code></pre>
<p>但若被设置成 0，则不关闭任何中断，0 也是缺省值。</p>
<pre><code class="pre-wrap"><span style="overflow-x: auto; max-width:100%; display:inline;"><code>MOV     R0, #0;
MSR     BASEPRI, R0;
</code></span></code></pre>
<h3 id="freertos">FreeRTOS 开关中断</h3>
<p>FreeRTOS 开关中断的函数为：</p>
<pre><code class="pre-wrap"><span style="overflow-x: auto; max-width:100%; display:inline;"><code class="language-c">/* portmacro.h */

#define portDISABLE_INTERRUPTS()                  vPortRaiseBASEPRI()
#define portENABLE_INTERRUPTS()                   vPortSetBASEPRI( 0 )

/*-----------------------------------------------------------*/

static portFORCE_INLINE void vPortRaiseBASEPRI( void )
{
    /* 本文注释：向 basepri 寄存器写入宏定义的值 */
    /* 优先级 &lt;数值&gt; 大于 configMAX_SYSCALL_INTERRUPT_PRIORITY 的中断被屏蔽  */
    /* Cortex-M 中数值越大优先级越低 */
    uint32_t ulNewBASEPRI = configMAX_SYSCALL_INTERRUPT_PRIORITY;

    __asm
    {
        /* Set BASEPRI to the max syscall priority to effect a critical
         * section. */
/* *INDENT-OFF* */
        msr basepri, ulNewBASEPRI
        dsb
        isb
/* *INDENT-ON* */
    }
}
/*-----------------------------------------------------------*/

static portFORCE_INLINE void vPortSetBASEPRI( uint32_t ulBASEPRI )
{
    __asm
    {
        /* Barrier instructions are not used as this function is only used to
         * lower the BASEPRI value. */
/* *INDENT-OFF* */
        msr basepri, ulBASEPRI  /* 本文注释：向 basepri 寄存器写值 ulBASEPRI */
/* *INDENT-ON* */               /* 优先级 &lt;数值&gt; 大于 ulBASEPRI 的中断被屏蔽  */
    }                           /* Cortex-M 中数值越大优先级越低 */
}
/*-----------------------------------------------------------*/
</code></span></code></pre>
<h3 id="_1">临界段代码</h3>
<pre><code class="pre-wrap"><span style="overflow-x: auto; max-width:100%; display:inline;"><code class="language-c">/**
 * task. h
 *
 * Macro to mark the start of a critical code region.  Preemptive context
 * switches cannot occur when in a critical region.
 *
 * NOTE: This may alter the stack (depending on the portable implementation)
 * so must be used with care!
 *
 * \defgroup taskENTER_CRITICAL taskENTER_CRITICAL
 * \ingroup SchedulerControl
 */
/* 本文注释：提供 任务 级别的 进入 临界区保护 */
#define taskENTER_CRITICAL()               portENTER_CRITICAL() 
/* 本文注释：提供 中断 级别的 进入 临界区保护 */
#define taskENTER_CRITICAL_FROM_ISR()      portSET_INTERRUPT_MASK_FROM_ISR()

/**
 * task. h
 *
 * Macro to mark the end of a critical code region.  Preemptive context
 * switches cannot occur when in a critical region.
 *
 * NOTE: This may alter the stack (depending on the portable implementation)
 * so must be used with care!
 *
 * \defgroup taskEXIT_CRITICAL taskEXIT_CRITICAL
 * \ingroup SchedulerControl
 */
/* 本文注释：提供 任务 级别的 退出 临界区保护 */
#define taskEXIT_CRITICAL()                portEXIT_CRITICAL()
/* 本文注释：提供 中断 级别的 退出 临界区保护 */
#define taskEXIT_CRITICAL_FROM_ISR( x )    portCLEAR_INTERRUPT_MASK_FROM_ISR( x )

</code></span></code></pre>
<h4 id="_2">提供 任务 级别的 临界区保护</h4>
<p>（支持临界区嵌套）</p>
<pre><code class="pre-wrap"><span style="overflow-x: auto; max-width:100%; display:inline;"><code class="language-C">/* portmacro.h */
    #define portENTER_CRITICAL()                      vPortEnterCritical()
    #define portEXIT_CRITICAL()                       vPortExitCritical()

/*-----------------------------------------------------------*/

void vPortEnterCritical( void )
{
    portDISABLE_INTERRUPTS(); /* 本文注释：关闭中断 */
    uxCriticalNesting++;

    /* This is not the interrupt safe version of the enter critical function so
     * assert() if it is being called from an interrupt context.  Only API
     * functions that end in &quot;FromISR&quot; can be used in an interrupt.  Only assert if
     * the critical nesting count is 1 to protect against recursive calls if the
     * assert function also uses a critical section. */
    if( uxCriticalNesting == 1 )
    {
        configASSERT( ( portNVIC_INT_CTRL_REG &amp; portVECTACTIVE_MASK ) == 0 );
    }
}
/*-----------------------------------------------------------*/

void vPortExitCritical( void )
{
    configASSERT( uxCriticalNesting );
    uxCriticalNesting--;

    if( uxCriticalNesting == 0 )
    {
        portENABLE_INTERRUPTS(); /* 本文注释：使能中断 */
    }
}
/*-----------------------------------------------------------*/
</code></span></code></pre>
<p>任务级临界区保护的使用方法：</p>
<pre><code class="pre-wrap"><span style="overflow-x: auto; max-width:100%; display:inline;"><code class="language-c">void taskCritical_test(void)
{
    while (1)
    {
        taskENTER_CRITICAL();   // 进入临界区
        total_num += 1;         // 临界区
        taskEXIT_CRITICAL();    // 退出临界区
    }
}
</code></span></code></pre>
<h4 id="_3">提供 中断 级别的 临界区保护</h4>
<pre><code class="pre-wrap"><span style="overflow-x: auto; max-width:100%; display:inline;"><code class="language-C">    #define portSET_INTERRUPT_MASK_FROM_ISR()         ulPortRaiseBASEPRI()
    #define portCLEAR_INTERRUPT_MASK_FROM_ISR( x )    vPortSetBASEPRI( x )

/*-----------------------------------------------------------*/

    static portFORCE_INLINE uint32_t ulPortRaiseBASEPRI( void )
    {
        uint32_t ulReturn, ulNewBASEPRI = configMAX_SYSCALL_INTERRUPT_PRIORITY;

        __asm
        {
            /* Set BASEPRI to the max syscall priority to effect a critical
             * section. */
/* *INDENT-OFF* */
            mrs ulReturn, basepri
            msr basepri, ulNewBASEPRI
            dsb
            isb
/* *INDENT-ON* */
        }

        return ulReturn;
    }
/*-----------------------------------------------------------*/

    static portFORCE_INLINE void vPortSetBASEPRI( uint32_t ulBASEPRI )
    {
        __asm
        {
            /* Barrier instructions are not used as this function is only used to
             * lower the BASEPRI value. */
/* *INDENT-OFF* */
            msr basepri, ulBASEPRI
/* *INDENT-ON* */
        }
    }
/*-----------------------------------------------------------*/
</code></span></code></pre>
<p>中断级临界区保护的使用方法：</p>
<pre><code class="pre-wrap"><span style="overflow-x: auto; max-width:100%; display:inline;"><code class="language-c">void TIM3_IRQHandler(void)
{
    if (TIM_GetITstatus(TIM3, TIM_IT_Update) != RESET)
    {
        status_value = taskENTER_CRITICAL_FROM_ISR();   // 进入临界区
        total_num += 1;                                 // 临界区
        taskEXIT_CRITICAL_FROM_ISR(status_value);       // 退出临界区
        USART_ClearITPendingBit(TIM3, TIM_IT_Update);
    }
}
</code></span></code></pre>
<h3 id="_4">任务控制块</h3>
<pre><code class="pre-wrap"><span style="overflow-x: auto; max-width:100%; display:inline;"><code class="language-c">/*
 * Task control block.  A task control block (TCB) is allocated for each task,
 * and stores task state information, including a pointer to the task's context
 * (the task's run time environment, including register values)
 */
typedef struct tskTaskControlBlock
{
    volatile StackType_t    *pxTopOfStack;  /* 任务堆栈栈顶 */

    #if ( portUSING_MPU_WRAPPERS == 1 )
        xMPU_SETTINGS   xMPUSettings;       /* MPU 相关设置 */
    #endif

    ListItem_t          xStateListItem;     /* 状态列表项 */
    ListItem_t          xEventListItem;     /* 事件列表项 */
    UBaseType_t         uxPriority;         /* 任务优先级 */
    StackType_t         *pxStack;           /* 任务堆栈起始地址 */
    char                pcTaskName[ configMAX_TASK_NAME_LEN ];  /* 任务名字 */

    #if ( portSTACK_GROWTH &gt; 0 )
        StackType_t     *pxEndOfStack;      /* 任务堆栈栈底 */
    #endif

    #if ( portCRITICAL_NESTING_IN_TCB == 1 )
        UBaseType_t     uxCriticalNesting;  /* 临界区嵌套深度 */
    #endif

    #if ( configUSE_TRACE_FACILITY == 1 )   /* trace 或 debug 的时候用到 */
        UBaseType_t     uxTCBNumber;        
        UBaseType_t     uxTaskNumber;
    #endif

    #if ( configUSE_MUTEXES == 1 )
        UBaseType_t     uxBasePriority;     /* 任务基础优先级，优先级反转的时候用到 */
        UBaseType_t     uxMutexesHeld;      /* 任务获取到的互斥信号量个数 */
    #endif

    #if ( configUSE_APPLICATION_TASK_TAG == 1 )
        TaskHookFunction_t pxTaskTag;
    #endif

    #if( configNUM_THREAD_LOCAL_STORAGE_POINTERS &gt; 0 )  /* 与本地存储有关 */
        void *pvThreadLocalStoragePointers[ configNUM_THREAD_LOCAL_STORAGE_POINTERS ];
    #endif

    #if( configGENERATE_RUN_TIME_STATS == 1 )
        uint32_t        ulRunTimeCounter;   /* 用来记录任务运行总时间 */
    #endif

    #if ( configUSE_NEWLIB_REENTRANT == 1 )
        struct  _reent xNewLib_reent;       /* newlib 结构体变量 */
    #endif


    #if( configUSE_TASK_NOTIFICATIONS == 1 )/* 任务通知相关变量 */
        volatile uint32_t ulNotifiedValue;  /* 任务通知值 */
        volatile uint8_t ucNotifyState;     /* 任务通知状态 */
    #endif

    #if( tskSTATIC_AND_DYNAMIC_ALLOCATION_POSSIBLE != 0 )
        /* 用来标记任务是动态创建的还是静态创建的，
            如果是静态创建的就为 pdTURE，
            如果是动态创建的就为 pdFALSE */
        uint8_t ucStaticallyAllocated;
    #endif

    #if( INCLUDE_xTaskAbortDelay == 1 )
        uint8_t ucDelayAborted;
    #endif

} tskTCB;

/* The old tskTCB name is maintained above then typedefed to the new TCB_t name
below to enable the use of older kernel aware debuggers. */
typedef tskTCB TCB_t;
</code></span></code></pre>
<h4 id="_5">堆栈大小</h4>
<pre><code class="pre-wrap"><span style="overflow-x: auto; max-width:100%; display:inline;"><code class="language-c">/* portmacro.h */

#define portCHAR        char
#define portFLOAT       float
#define portDOUBLE      double
#define portLONG        long
#define portSHORT       short
#define portSTACK_TYPE  uint32_t
#define portBASE_TYPE   long

/* 
    StackType_t 为任务堆栈的数据类型，实际上为 uint32_t 即 4 个字节，
    实际设置中任务堆栈的 数据单位为 word，因此 如果设置堆栈大小为 128 实际上有 512 个字节。
*/
typedef portSTACK_TYPE StackType_t; 
typedef long BaseType_t;
typedef unsigned long UBaseType_t;
</code></span></code></pre>
<h2 id="freertos-api">FreeRTOS 任务相关 API 函数</h2>
<h3 id="api">任务创建和删除 API 函数</h3>
<div class="pure-table-scrollable"><table class="pure-table pure-table-horizontal">
<thead>
<tr>
<th>函数</th>
<th>描述</th>
</tr>
</thead>
<tbody>
<tr>
<td><span style="overflow-x: auto; max-width:100%; display:inline;"><code>xTaskCreate()</code></span></td>
<td>使用动态方法创建一个任务。</td>
</tr>
<tr>
<td><span style="overflow-x: auto; max-width:100%; display:inline;"><code>xTaskCreateStatic()</code></span></td>
<td>使用静态方法创建一个任务。</td>
</tr>
<tr>
<td><span style="overflow-x: auto; max-width:100%; display:inline;"><code>xTaskCreateRestricted()</code></span></td>
<td>创建一个使用 MPU 进行限制的任务，相关内存使用动态内存分配。</td>
</tr>
<tr>
<td><span style="overflow-x: auto; max-width:100%; display:inline;"><code>xTaskDelete()</code></span></td>
<td>删除一个任务。</td>
</tr>
</tbody>
</table></div>
<h4 id="xtaskcreate"><span style="overflow-x: auto; max-width:100%; display:inline;"><code>xTaskCreate()</code></span> 动态创建任务</h4>
<p>此函数可以动态创建一个任务，任务需要 RAM 来保存与任务有关的信息状态（任务控制块），任务也需要一定的 RAM 来作为任务堆栈。</p>
<p>如果使用此函数创建任务，这些需要的 RAM 就会从 FreeRTOS 的堆中分配，因此必须提供内存管理文件，默认我们使用 heap_4.c 这个内存管理文件，而且宏 <span style="overflow-x: auto; max-width:100%; display:inline;"><code>configSUPPORT_DYNAMIC_ALLOCATION</code></span> 必须为 1。</p>
<p>新创建的任务默认是就绪态的。</p>
<p>不管在任务调度器启动前还是启动后都可以创建任务。</p>
<pre><code class="pre-wrap"><span style="overflow-x: auto; max-width:100%; display:inline;"><code class="language-C">/* task.h */

BaseType_t xTaskCreate( TaskFunction_t      pxTaskCode,
                       const char * const   pcName,
                       const uint16_t       usStackDepth,
                       void * const         pvParameters,
                       UBaseType_t          uxPriority,
                       TaskHandle_t * const pxCreatedTask ) PRIVILEGED_FUNCTION; 
/*lint !e971 Unqualified char types are allowed for strings and single characters only. */
</code></span></code></pre>
<div class="pure-table-scrollable"><table class="pure-table pure-table-horizontal">
<thead>
<tr>
<th>参数</th>
<th>描述</th>
</tr>
</thead>
<tbody>
<tr>
<td><span style="overflow-x: auto; max-width:100%; display:inline;"><code>pxTaskCode</code></span></td>
<td>任务函数。</td>
</tr>
<tr>
<td><span style="overflow-x: auto; max-width:100%; display:inline;"><code>pcName</code></span></td>
<td>任务名字。<br />长度不能超过 <span style="overflow-x: auto; max-width:100%; display:inline;"><code>configMAX_TASK_NAME_LEN</code></span>。</td>
</tr>
<tr>
<td><span style="overflow-x: auto; max-width:100%; display:inline;"><code>usStackDepth</code></span></td>
<td>任务堆栈大小，单位为字（word）。<br />实际申请到的栈大小是 <span style="overflow-x: auto; max-width:100%; display:inline;"><code>usStackDepth</code></span> 的四倍（一个word 为 4 个字节）。<br />其中空闲任务的任务堆栈大小为 <span style="overflow-x: auto; max-width:100%; display:inline;"><code>configMINIMAL_STACK_SIZE</code></span>。</td>
</tr>
<tr>
<td><span style="overflow-x: auto; max-width:100%; display:inline;"><code>pvParameters</code></span></td>
<td>传递给任务函数的参数。</td>
</tr>
<tr>
<td><span style="overflow-x: auto; max-width:100%; display:inline;"><code>uxPriority</code></span></td>
<td>任务优先级。<br />范围 <span style="overflow-x: auto; max-width:100%; display:inline;"><code>0 ~ configMAX_PRIORITIES-1</code></span>。</td>
</tr>
<tr>
<td><span style="overflow-x: auto; max-width:100%; display:inline;"><code>pxCreatedTask</code></span></td>
<td>任务句柄，任务创建成功以后会返回此任务的任务句柄。<br />这个句柄其实就是任务的任务堆栈。<br />此参数用来保存这个任务句柄，其他 API 函数可能会使用到这个句柄。</td>
</tr>
</tbody>
</table></div>
<p>返回值：</p>
<p><span style="overflow-x: auto; max-width:100%; display:inline;"><code>pdPASS</code></span>：任务创建成功。</p>
<p><span style="overflow-x: auto; max-width:100%; display:inline;"><code>errCOULD_NOT_ALLOCATE_REQUIRED_MEMORY</code></span>：任务创建失败，堆栈内存不足。</p>
<pre><code class="pre-wrap"><span style="overflow-x: auto; max-width:100%; display:inline;"><code class="language-c">#define pdFALSE         ( ( BaseType_t ) 0 )
#define pdTRUE          ( ( BaseType_t ) 1 )

#define pdPASS          ( pdTRUE )
#define pdFAIL          ( pdFALSE )
#define errQUEUE_EMPTY  ( ( BaseType_t ) 0 )
#define errQUEUE_FULL   ( ( BaseType_t ) 0 )

/* FreeRTOS error definitions. */
#define errCOULD_NOT_ALLOCATE_REQUIRED_MEMORY   ( -1 )
#define errQUEUE_BLOCKED                        ( -4 )
#define errQUEUE_YIELD                          ( -5 )
</code></span></code></pre>
<h4 id="xtaskcreatestatic"><span style="overflow-x: auto; max-width:100%; display:inline;"><code>xTaskCreateStatic()</code></span> 静态创建任务</h4>
<pre><code class="pre-wrap"><span style="overflow-x: auto; max-width:100%; display:inline;"><code class="language-c">/* task.h */

TaskHandle_t xTaskCreateStatic( TaskFunction_t      pxTaskCode,
                               const char * const   pcName,
                               const uint32_t       ulStackDepth,
                               void * const         pvParameters,
                               UBaseType_t          uxPriority,
                               StackType_t * const  puxStackBuffer,
                               StaticTask_t * const pxTaskBuffer ) PRIVILEGED_FUNCTION; 
/*lint !e971 Unqualified char types are allowed for strings and single characters only. */
</code></span></code></pre>
<div class="pure-table-scrollable"><table class="pure-table pure-table-horizontal">
<thead>
<tr>
<th>参数</th>
<th>描述</th>
</tr>
</thead>
<tbody>
<tr>
<td><span style="overflow-x: auto; max-width:100%; display:inline;"><code>pxTaskCode</code></span></td>
<td>任务函数。</td>
</tr>
<tr>
<td><span style="overflow-x: auto; max-width:100%; display:inline;"><code>pcName</code></span></td>
<td>任务名字。<br />长度不能超过 <span style="overflow-x: auto; max-width:100%; display:inline;"><code>configMAX_TASK_NAME_LEN</code></span>。</td>
</tr>
<tr>
<td><span style="overflow-x: auto; max-width:100%; display:inline;"><code>usStackDepth</code></span></td>
<td>任务堆栈大小，单位为字（word）。<br />实际申请到的栈大小是 <span style="overflow-x: auto; max-width:100%; display:inline;"><code>usStackDepth</code></span> 的四倍（一个word 为 4 个字节）。<br />其中空闲任务的任务堆栈大小为 <span style="overflow-x: auto; max-width:100%; display:inline;"><code>configMINIMAL_STACK_SIZE</code></span>。</td>
</tr>
<tr>
<td><span style="overflow-x: auto; max-width:100%; display:inline;"><code>pvParameters</code></span></td>
<td>传递给任务函数的参数。</td>
</tr>
<tr>
<td><span style="overflow-x: auto; max-width:100%; display:inline;"><code>uxPriority</code></span></td>
<td>任务优先级。<br />范围 <span style="overflow-x: auto; max-width:100%; display:inline;"><code>0 ~ configMAX_PRIORITIES-1</code></span>。</td>
</tr>
<tr>
<td>~~<span style="overflow-x: auto; max-width:100%; display:inline;"><code>pxCreatedTask</code></span>~~</td>
<td>~~任务句柄，任务创建成功以后会返回此任务的任务句柄。<br />这个句柄其实就是任务的任务堆栈。<br />此参数用来保存这个任务句柄，其他 API 函数可能会使用到这个句柄。~~</td>
</tr>
<tr>
<td><span style="overflow-x: auto; max-width:100%; display:inline;"><code>puxStackBuffer</code></span></td>
<td>任务堆栈，一般为数组，数组类型为 <span style="overflow-x: auto; max-width:100%; display:inline;"><code>StackType_t</code></span> 类型。</td>
</tr>
<tr>
<td><span style="overflow-x: auto; max-width:100%; display:inline;"><code>pxTaskBuffer</code></span></td>
<td>任务控制块。</td>
</tr>
</tbody>
</table></div>
<p>返回值：</p>
<p><span style="overflow-x: auto; max-width:100%; display:inline;"><code>NULL</code></span>：任务创建失败，<span style="overflow-x: auto; max-width:100%; display:inline;"><code>puxStackBuffer</code></span> 或 <span style="overflow-x: auto; max-width:100%; display:inline;"><code>pxTaskBuffer</code></span> 为 <span style="overflow-x: auto; max-width:100%; display:inline;"><code>NULL</code></span> 时会导致这个错误。</p>
<p>其他值：任务创建成功。</p>
<blockquote class="content-quote">
<p>&hellip;</p>
</blockquote>
<h2 id="freertos_1">FreeRTOS 列表和列表项</h2>
<h2 id="_6">列表</h2>
<p>列表是 FreeRTOS 中的一个数据结构，概念上和链表类似，列表用来跟踪 FreeRTOS 中的任务。</p>
<pre><code class="pre-wrap"><span style="overflow-x: auto; max-width:100%; display:inline;"><code class="language-c">/* list.h */

typedef struct xLIST
{
    listFIRST_LIST_INTEGRITY_CHECK_VALUE
    configLIST_VOLATILE UBaseType_t     uxNumberOfItems;
    ListItem_t * configLIST_VOLATILE    pxIndex;
    MiniListItem_t                      xListEnd;
    listSECOND_LIST_INTEGRITY_CHECK_VALUE
} List_t;
</code></span></code></pre>
<p>结构体成员说明：</p>
<div class="pure-table-scrollable"><table class="pure-table pure-table-horizontal">
<thead>
<tr>
<th>结构体成员</th>
<th>描述</th>
</tr>
</thead>
<tbody>
<tr>
<td><span style="overflow-x: auto; max-width:100%; display:inline;"><code>listFIRST_LIST_INTEGRITY_CHECK_VALUE</code></span></td>
<td>用于检查列表完整性。<br />configUSE<em>LIST</em>DATA<em>INTEGRITY</em>CHECK<em>BYTES 设置为 1 之后，会在此处添加一个变量，在初始化列表的时候会给该变量写入一个特殊值。（默认不开启）<br />Set to a known value if configUSE</em>LIST<em>DATA</em>INTEGRITY<em>CHECK</em>BYTES is set to 1.</td>
</tr>
<tr>
<td><span style="overflow-x: auto; max-width:100%; display:inline;"><code>uxNumberOfItems</code></span></td>
<td>用来记录列表中列表项的数量。</td>
</tr>
<tr>
<td><span style="overflow-x: auto; max-width:100%; display:inline;"><code>pxIndex</code></span></td>
<td>用来记录当前列表项索引号，用于遍历列表。<br />Used to walk through the list. Points to the last item returned by a call to listGET<em>OWNER</em>OF<em>NEXT</em>ENTRY ().</td>
</tr>
<tr>
<td><span style="overflow-x: auto; max-width:100%; display:inline;"><code>xListEnd</code></span></td>
<td>列表项中最后一个列表项，用来表示列表结束。<br />类型 MiniListItem_t 是一个迷你列表项。<br />List item that contains the maximum possible item value meaning it is always at the end of the list and is therefore used as a marker.</td>
</tr>
<tr>
<td><span style="overflow-x: auto; max-width:100%; display:inline;"><code>listSECOND_LIST_INTEGRITY_CHECK_VALUE</code></span></td>
<td>用于检查列表完整性。<br />configUSE<em>LIST</em>DATA<em>INTEGRITY</em>CHECK<em>BYTES 设置为 1 之后，会在此处添加一个变量，在初始化列表的时候会给该变量写入一个特殊值。（默认不开启）<br />Set to a known value if configUSE</em>LIST<em>DATA</em>INTEGRITY<em>CHECK</em>BYTES is set to 1.</td>
</tr>
</tbody>
</table></div>
<p><img class="pure-img" src="https://zromyk.gitee.io/myblog-figurebed/post/FreeRTOS.assets/image-20210427171420341.png" alt="image-20210427171420341" style="zoom: 50%;" /></p>
<p>列表完整性检查相关宏定义：</p>
<pre><code class="pre-wrap"><span style="overflow-x: auto; max-width:100%; display:inline;"><code class="language-C">/* list.h */

#if( configUSE_LIST_DATA_INTEGRITY_CHECK_BYTES == 0 )
    // ...
#else
    /* Define macros that add new members into the list structures. */
    #define listFIRST_LIST_ITEM_INTEGRITY_CHECK_VALUE       TickType_t xListItemIntegrityValue1;
    #define listSECOND_LIST_ITEM_INTEGRITY_CHECK_VALUE      TickType_t xListItemIntegrityValue2;
    #define listFIRST_LIST_INTEGRITY_CHECK_VALUE            TickType_t xListIntegrityValue1;
    #define listSECOND_LIST_INTEGRITY_CHECK_VALUE           TickType_t xListIntegrityValue2;
    // ...
#endif /* configUSE_LIST_DATA_INTEGRITY_CHECK_BYTES */
</code></span></code></pre>
<h2 id="_7">列表项</h2>
<p>列表项就是存放在列表中的项目，FreeRTOS 提供了两种列表项：列表项，迷你列表项。</p>
<pre><code class="pre-wrap"><span style="overflow-x: auto; max-width:100%; display:inline;"><code class="language-c">/* list.h */

struct xLIST_ITEM
{
    listFIRST_LIST_ITEM_INTEGRITY_CHECK_VALUE
    configLIST_VOLATILE TickType_t              xItemValue;
    struct xLIST_ITEM * configLIST_VOLATILE     pxNext;
    struct xLIST_ITEM * configLIST_VOLATILE     pxPrevious;
    void *                                      pvOwner;
    void * configLIST_VOLATILE                  pvContainer;
    listSECOND_LIST_ITEM_INTEGRITY_CHECK_VALUE
};
typedef struct xLIST_ITEM ListItem_t;
/* For some reason lint wants this as two separate definitions. */
</code></span></code></pre>
<p>结构体成员说明：</p>
<div class="pure-table-scrollable"><table class="pure-table pure-table-horizontal">
<thead>
<tr>
<th>结构体成员</th>
<th>描述</th>
</tr>
</thead>
<tbody>
<tr>
<td><span style="overflow-x: auto; max-width:100%; display:inline;"><code>listFIRST_LIST_ITEM_INTEGRITY_CHECK_VALUE</code></span></td>
<td>Set to a known value if configUSE<em>LIST</em>DATA<em>INTEGRITY</em>CHECK_BYTES is set to 1.</td>
</tr>
<tr>
<td><span style="overflow-x: auto; max-width:100%; display:inline;"><code>xItemValue</code></span></td>
<td>列表项的值。<br />The value being listed.  In most cases this is used to sort the list in descending order.</td>
</tr>
<tr>
<td><span style="overflow-x: auto; max-width:100%; display:inline;"><code>pxNext</code></span></td>
<td>指向下一个列表项。<br />Pointer to the next ListItem_t in the list.</td>
</tr>
<tr>
<td><span style="overflow-x: auto; max-width:100%; display:inline;"><code>pxPrevious</code></span></td>
<td>指向前一个列表项。<br />Pointer to the previous ListItem_t in the list.</td>
</tr>
<tr>
<td><span style="overflow-x: auto; max-width:100%; display:inline;"><code>pvOwner</code></span></td>
<td>记录此列表项的拥有者，通常是任务控制块。<br />Pointer to the object (normally a TCB) that contains the list item.  There is therefore a two way link between the object containing the list item and the list item itself.</td>
</tr>
<tr>
<td><span style="overflow-x: auto; max-width:100%; display:inline;"><code>pvContainer</code></span></td>
<td>记录此列表项归属于哪个列表。<br />Pointer to the list in which this list item is placed (if any).</td>
</tr>
<tr>
<td><span style="overflow-x: auto; max-width:100%; display:inline;"><code>listSECOND_LIST_ITEM_INTEGRITY_CHECK_VALUE</code></span></td>
<td>Set to a known value if configUSE<em>LIST</em>DATA<em>INTEGRITY</em>CHECK_BYTES is set to 1.</td>
</tr>
</tbody>
</table></div>
<p><img class="pure-img" src="https://zromyk.gitee.io/myblog-figurebed/post/FreeRTOS.assets/image-20210427172617339.png" alt="image-20210427172617339" style="zoom: 67%;" /></p>
<p><span style="overflow-x: auto; max-width:100%; display:inline;"><code>pvOwner</code></span> 和 <span style="overflow-x: auto; max-width:100%; display:inline;"><code>pvContainer</code></span> 的区别：</p>
<p>在任务控制块 <span style="overflow-x: auto; max-width:100%; display:inline;"><code>TCB_t</code></span>中有两个变量：（注意这两个变量的类型都是 <span style="overflow-x: auto; max-width:100%; display:inline;"><code>ListItem_t</code></span> 列表项）</p>
<pre><code class="pre-wrap"><span style="overflow-x: auto; max-width:100%; display:inline;"><code class="language-c">    ListItem_t          xStateListItem;     /* 状态列表项 */
    ListItem_t          xEventListItem;     /* 事件列表项 */
</code></span></code></pre>
<p>当创建一个任务以后，</p>
<ul>
<li><span style="overflow-x: auto; max-width:100%; display:inline;"><code>xStateListItem</code></span> 的 <span style="overflow-x: auto; max-width:100%; display:inline;"><code>pvOwner</code></span> 变量就指向这个任务的任务控制块，表示 <span style="overflow-x: auto; max-width:100%; display:inline;"><code>xStateListItem</code></span> 属于此任务。</li>
<li>当任务就绪态之后 <span style="overflow-x: auto; max-width:100%; display:inline;"><code>xStateListItem</code></span> 变量的 <span style="overflow-x: auto; max-width:100%; display:inline;"><code>pvContainer</code></span> 指向就绪列表，表明此列表项在就绪列表中。</li>
</ul>
<h2 id="_8">迷你列表项</h2>
<pre><code class="pre-wrap"><span style="overflow-x: auto; max-width:100%; display:inline;"><code class="language-c">/* list.h */

struct xMINI_LIST_ITEM
{
    listFIRST_LIST_ITEM_INTEGRITY_CHECK_VALUE           
    /*&lt; Set to a known value if configUSE_LIST_DATA_INTEGRITY_CHECK_BYTES is set to 1. */
    configLIST_VOLATILE TickType_t              xItemValue;
    struct xLIST_ITEM * configLIST_VOLATILE     pxNext;
    struct xLIST_ITEM * configLIST_VOLATILE     pxPrevious;
};
typedef struct xMINI_LIST_ITEM MiniListItem_t;
</code></span></code></pre>
<p>结构体成员说明：</p>
<div class="pure-table-scrollable"><table class="pure-table pure-table-horizontal">
<thead>
<tr>
<th>结构体成员</th>
<th>描述</th>
</tr>
</thead>
<tbody>
<tr>
<td><span style="overflow-x: auto; max-width:100%; display:inline;"><code>listFIRST_LIST_ITEM_INTEGRITY_CHECK_VALUE</code></span></td>
<td>Set to a known value if configUSE<em>LIST</em>DATA<em>INTEGRITY</em>CHECK_BYTES is set to 1.</td>
</tr>
<tr>
<td><span style="overflow-x: auto; max-width:100%; display:inline;"><code>xItemValue</code></span></td>
<td>列表项的值。<br />The value being listed.  In most cases this is used to sort the list in descending order.</td>
</tr>
<tr>
<td><span style="overflow-x: auto; max-width:100%; display:inline;"><code>pxNext</code></span></td>
<td>指向下一个列表项。<br />Pointer to the next ListItem_t in the list.</td>
</tr>
<tr>
<td><span style="overflow-x: auto; max-width:100%; display:inline;"><code>pxPrevious</code></span></td>
<td>指向前一个列表项。<br />Pointer to the previous ListItem_t in the list.</td>
</tr>
</tbody>
</table></div>
<h2 id="_9">列表和列表项初始化</h2>
<h3 id="_10">列表初始化</h3>
<pre><code class="pre-wrap"><span style="overflow-x: auto; max-width:100%; display:inline;"><code class="language-c">/* list.c */

void vListInitialise( List_t * const pxList )
{
    /* The list structure contains a list item which is used to mark the
    end of the list.  To initialise the list the list end is inserted
    as the only list entry. */
    pxList-&gt;pxIndex = ( ListItem_t * ) &amp;( pxList-&gt;xListEnd );           
    /* 
        本文注释：
        pxIndex 表示列表项的索引号，此时列表只有一个列表项，就是 xListEnd 
    */

    /* The list end value is the highest possible value in the list to
    ensure it remains at the end of the list. */
    pxList-&gt;xListEnd.xItemValue = portMAX_DELAY;
    /* 
        本文注释：
        xItemValue 列表项值初始化为 portMAX_DELAY
        portMAX_DELAY 是宏定义，在 portmacro.h 中，根据 MCU 不同，portMAX_DELAY 的值也不同
    */

    /* The list end next and previous pointers point to itself so we know
    when the list is empty. */
    pxList-&gt;xListEnd.pxNext = ( ListItem_t * ) &amp;( pxList-&gt;xListEnd );   
    pxList-&gt;xListEnd.pxPrevious = ( ListItem_t * ) &amp;( pxList-&gt;xListEnd );

    pxList-&gt;uxNumberOfItems = ( UBaseType_t ) 0U;
    /* 
        本文注释：
        uxNumberOfItems 表示列表项数量，此时没有列表项，值为 0（不包括 xListEnd）
    */

    /* Write known values into the list if
    configUSE_LIST_DATA_INTEGRITY_CHECK_BYTES is set to 1. */
    listSET_LIST_INTEGRITY_CHECK_1_VALUE( pxList );
    listSET_LIST_INTEGRITY_CHECK_2_VALUE( pxList );
    /* 
        本文注释：
        初始化列表项中用于完整性检查字段，当 configUSE_LIST_DATA_INTEGRITY_CHECK_BYTES 为 1 有效。
        同样根据 MCU 不同，写入的值也不同
    */
}
</code></span></code></pre>
<h3 id="_11">列表项初始化</h3>
<pre><code class="pre-wrap"><span style="overflow-x: auto; max-width:100%; display:inline;"><code class="language-c">/* list.c */

void vListInitialiseItem( ListItem_t * const pxItem )
{
    /* Make sure the list item is not recorded as being on a list. */
    pxItem-&gt;pvContainer = NULL;

    /* Write known values into the list item if
    configUSE_LIST_DATA_INTEGRITY_CHECK_BYTES is set to 1. */
    listSET_FIRST_LIST_ITEM_INTEGRITY_CHECK_VALUE( pxItem );
    listSET_SECOND_LIST_ITEM_INTEGRITY_CHECK_VALUE( pxItem );
    /* 
        本文注释：
        初始化列表项中用于完整性检查字段，当 configUSE_LIST_DATA_INTEGRITY_CHECK_BYTES 为 1 有效。
        同样根据 MCU 不同，写入的值也不同
    */
}
</code></span></code></pre>
<h3 id="_12">列表项插入</h3>
<h4 id="_13">按列表项的值插入</h4>
<pre><code class="pre-wrap"><span style="overflow-x: auto; max-width:100%; display:inline;"><code class="language-c">void vListInsert( List_t * const pxList, ListItem_t * const pxNewListItem )
{
    ListItem_t *pxIterator;
    const TickType_t xValueOfInsertion = pxNewListItem-&gt;xItemValue;
    /* 
        本文注释：
        获取要插入的列表项值，即列表项成员变量 xItemValue，根据这个值来决定插入位置。
    */

    /* Only effective when configASSERT() is also defined, these tests may catch
    the list data structures being overwritten in memory.  They will not catch
    data errors caused by incorrect configuration or use of FreeRTOS. */
    listTEST_LIST_INTEGRITY( pxList );
    listTEST_LIST_ITEM_INTEGRITY( pxNewListItem );
    /* 
        本文注释：
        检查列表和列表项的完整性，其实就是检查列表和列表项中用于完整性检查的变量值是否被改变。
        这些变量的值在列表和列表初始化的时候就被写入了，这两行代码需要用到 configASSERT() 函数。
    */

    /* Insert the new list item into the list, sorted in xItemValue order.

    If the list already contains a list item with the same item value then the
    new list item should be placed after it.  This ensures that TCB's which are
    stored in ready lists (all of which have the same xItemValue value) get a
    share of the CPU.  However, if the xItemValue is the same as the back marker
    the iteration loop below will not end.  Therefore the value is checked
    first, and the algorithm slightly modified if necessary. */
    if( xValueOfInsertion == portMAX_DELAY )
    {
        /* 
            本文注释：
            如果要插入的列表项的值等于 portMAX_DELAY，即列表项值为最大值，
            直接在列表的末尾插入。
        */
        pxIterator = pxList-&gt;xListEnd.pxPrevious;
        /* 
            本文注释：获取插入点。

            注意：在初始化列表的时候，xListEnd 的值也是 portMAX_DELAY，
            此时要插入的列表项的值也是 portMAX_DELAY，从上面这行代码看出，
            要插入的列表项会被放在 xListEnd 的前面。
        */
    }
    else
    {
        /* *** NOTE ***********************************************************
        If you find your application is crashing here then likely causes are
        listed below.  In addition see http://www.freertos.org/FAQHelp.html for
        more tips, and ensure configASSERT() is defined!
        http://www.freertos.org/a00110.html#configASSERT

            1) Stack overflow -
               see http://www.freertos.org/Stacks-and-stack-overflow-checking.html
            2) Incorrect interrupt priority assignment, especially on Cortex-M
               parts where numerically high priority values denote low actual
               interrupt priorities, which can seem counter intuitive.  See
               http://www.freertos.org/RTOS-Cortex-M3-M4.html and the definition
               of configMAX_SYSCALL_INTERRUPT_PRIORITY on
               http://www.freertos.org/a00110.html
            3) Calling an API function from within a critical section or when
               the scheduler is suspended, or calling an API function that does
               not end in &quot;FromISR&quot; from an interrupt.
            4) Using a queue or semaphore before it has been initialised or
               before the scheduler has been started (are interrupts firing
               before vTaskStartScheduler() has been called?).
        **********************************************************************/

        for( pxIterator = ( ListItem_t * ) &amp;( pxList-&gt;xListEnd ); pxIterator-&gt;pxNext-&gt;xItemValue &lt;= xValueOfInsertion; pxIterator = pxIterator-&gt;pxNext ) /*lint !e826 !e740 The mini list structure is used as the list end to save RAM.  This is checked and valid. */
        {
            /* There is nothing to do here, just iterating to the wanted
            insertion position. */
            /* 
                本文注释：
                遍历查找插入点，由于 for 是用来查找插入点的，因此 for 体内什么都不做。
            */
        }
    }

    pxNewListItem-&gt;pxNext = pxIterator-&gt;pxNext;
    pxNewListItem-&gt;pxNext-&gt;pxPrevious = pxNewListItem;
    pxNewListItem-&gt;pxPrevious = pxIterator;
    pxIterator-&gt;pxNext = pxNewListItem;
    /* 
        本文注释：插入列表项的过程。
    */

    /* Remember which list the item is in.  This allows fast removal of the
    item later. */
    pxNewListItem-&gt;pvContainer = ( void * ) pxList;
    /* 
        本文注释：记录列表项属于哪个列表。
    */

    ( pxList-&gt;uxNumberOfItems )++;
    /* 
        本文注释：列表的列表项数目 +1
    */
}
</code></span></code></pre>
<p><img class="pure-img" src="https://zromyk.gitee.io/myblog-figurebed/post/FreeRTOS.assets/image-20210427175554760.png" alt="image-20210427175554760" style="zoom: 67%;" /></p>
<p><img class="pure-img" src="https://zromyk.gitee.io/myblog-figurebed/post/FreeRTOS.assets/image-20210427175619937.png" alt="image-20210427175619937" style="zoom:67%;" /></p>
<p><img class="pure-img" src="https://zromyk.gitee.io/myblog-figurebed/post/FreeRTOS.assets/image-20210427175652637.png" alt="image-20210427175652637" style="zoom:67%;" /></p>
<h4 id="_14">在最后插入</h4>
<pre><code class="pre-wrap"><span style="overflow-x: auto; max-width:100%; display:inline;"><code class="language-c">void vListInsertEnd( List_t * const pxList, ListItem_t * const pxNewListItem )
{
    ListItem_t * const pxIndex = pxList-&gt;pxIndex;

    /* Only effective when configASSERT() is also defined, these tests may catch
    the list data structures being overwritten in memory.  They will not catch
    data errors caused by incorrect configuration or use of FreeRTOS. */
    listTEST_LIST_INTEGRITY( pxList );
    listTEST_LIST_ITEM_INTEGRITY( pxNewListItem );

    /* Insert a new list item into pxList, but rather than sort the list,
    makes the new list item the last item to be removed by a call to
    listGET_OWNER_OF_NEXT_ENTRY(). */
    pxNewListItem-&gt;pxNext = pxIndex;
    pxNewListItem-&gt;pxPrevious = pxIndex-&gt;pxPrevious;

    /* Only used during decision coverage testing. */
    mtCOVERAGE_TEST_DELAY();

    pxIndex-&gt;pxPrevious-&gt;pxNext = pxNewListItem;
    pxIndex-&gt;pxPrevious = pxNewListItem;

    /* Remember which list the item is in. */
    pxNewListItem-&gt;pvContainer = ( void * ) pxList;

    ( pxList-&gt;uxNumberOfItems )++;
}
</code></span></code></pre>
<h3 id="_15">列表项删除</h3>
<pre><code class="pre-wrap"><span style="overflow-x: auto; max-width:100%; display:inline;"><code class="language-c">UBaseType_t uxListRemove( ListItem_t * const pxItemToRemove )
{
    /* The list item knows which list it is in.  Obtain the list from the list
    item. */
    List_t * const pxList = ( List_t * ) pxItemToRemove-&gt;pvContainer;

    pxItemToRemove-&gt;pxNext-&gt;pxPrevious = pxItemToRemove-&gt;pxPrevious;
    pxItemToRemove-&gt;pxPrevious-&gt;pxNext = pxItemToRemove-&gt;pxNext;

    /* Only used during decision coverage testing. */
    mtCOVERAGE_TEST_DELAY();

    /* Make sure the index is left pointing to a valid item. */
    if( pxList-&gt;pxIndex == pxItemToRemove )
    {
        pxList-&gt;pxIndex = pxItemToRemove-&gt;pxPrevious;
    }
    else
    {
        mtCOVERAGE_TEST_MARKER();
    }

    pxItemToRemove-&gt;pvContainer = NULL;
    ( pxList-&gt;uxNumberOfItems )--;

    return pxList-&gt;uxNumberOfItems;
}
</code></span></code></pre>
<h2 id="freertos_2">FreeRTOS 任务切换</h2>
<h2 id="freertos_3">FreeRTOS 调度器开启和任务相关函数</h2>
<div class="pure-table-scrollable"><table class="pure-table pure-table-horizontal">
<thead>
<tr>
<th>函数</th>
<th>功能</th>
</tr>
</thead>
<tbody>
<tr>
<td><span style="overflow-x: auto; max-width:100%; display:inline;"><code>vTaskStartScheduler</code></span></td>
<td>任务调度器开启函数</td>
</tr>
<tr>
<td><span style="overflow-x: auto; max-width:100%; display:inline;"><code>xPortStartScheduler</code></span></td>
<td>内核相关硬件初始化函数</td>
</tr>
<tr>
<td></td>
<td></td>
</tr>
<tr>
<td></td>
<td></td>
</tr>
</tbody>
</table></div>
<h3 id="_16">任务调度器开启函数</h3>
<pre><code class="pre-wrap"><span style="overflow-x: auto; max-width:100%; display:inline;"><code class="language-c">/* tasks.c */

void vTaskStartScheduler( void )
{
    BaseType_t xReturn;

    /* Add the idle task at the lowest priority. */
    /*
        本文注释：
        创建空闲任务。
        根据宏 configSUPPORT_STATIC_ALLOCATION 决定是使用静态还是动态方法创建空闲任务。
        空闲任务的优先级为 tskIDLE_PRIORITY，值为 0，即空闲任务的优先级最低。

        #define tskIDLE_PRIORITY            ( ( UBaseType_t ) 0U )

        #ifndef portPRIVILEGE_BIT
            #define portPRIVILEGE_BIT       ( ( UBaseType_t ) 0x00 )
        #endif
    */
    #if( configSUPPORT_STATIC_ALLOCATION == 1 )
    {
        StaticTask_t *pxIdleTaskTCBBuffer = NULL;
        StackType_t *pxIdleTaskStackBuffer = NULL;
        uint32_t ulIdleTaskStackSize;

        /* The Idle task is created using user provided RAM - obtain the
        address of the RAM then create the idle task. */
        vApplicationGetIdleTaskMemory( &amp;pxIdleTaskTCBBuffer, &amp;pxIdleTaskStackBuffer, &amp;ulIdleTaskStackSize );
        xIdleTaskHandle = xTaskCreateStatic(    prvIdleTask,
                                                &quot;IDLE&quot;,
                                                ulIdleTaskStackSize,
                                                ( void * ) NULL,
                                                ( tskIDLE_PRIORITY | portPRIVILEGE_BIT ),
                                                pxIdleTaskStackBuffer,
                                                pxIdleTaskTCBBuffer ); 
        /*lint !e961 MISRA exception, justified as it is not a redundant explicit cast to all supported compilers. */

        if( xIdleTaskHandle != NULL )
        {
            xReturn = pdPASS;
        }
        else
        {
            xReturn = pdFAIL;
        }
    }
    #else
    {
        /* The Idle task is being created using dynamically allocated RAM. */
        xReturn = xTaskCreate(  prvIdleTask,
                                &quot;IDLE&quot;, configMINIMAL_STACK_SIZE,
                                ( void * ) NULL,
                                ( tskIDLE_PRIORITY | portPRIVILEGE_BIT ),
                                &amp;xIdleTaskHandle ); /*lint !e961 MISRA exception, justified as it is not a redundant explicit cast to all supported compilers. */
    }
    #endif /* configSUPPORT_STATIC_ALLOCATION */

    /*
        本文注释：使用软件定时器功能。
    */
    #if ( configUSE_TIMERS == 1 )
    {
        if( xReturn == pdPASS )
        {
            /*
                本文注释：
                使用软件定时器功能，还需要通过函数 xTimerCreateTimerTask() 来创建定时器服务任务。
            */
            xReturn = xTimerCreateTimerTask();
        }
        else
        {
            mtCOVERAGE_TEST_MARKER();
        }
    }
    #endif /* configUSE_TIMERS */

    /*
        本文注释：空闲任务和定时器任务创建成功。
    */
    if( xReturn == pdPASS )
    {
        /* Interrupts are turned off here, to ensure a tick does not occur
        before or during the call to xPortStartScheduler().  The stacks of
        the created tasks contain a status word with interrupts switched on
        so interrupts will automatically get re-enabled when the first task
        starts to run. */
        /*
            本文注释：关闭中断。
            注意：在 SVC 中断服务函数 vPortSVCHandler() 中会打开中断。
        */
        portDISABLE_INTERRUPTS();

        #if ( configUSE_NEWLIB_REENTRANT == 1 )
        {
            /* Switch Newlib's _impure_ptr variable to point to the _reent
            structure specific to the task that will run first. */
            _impure_ptr = &amp;( pxCurrentTCB-&gt;xNewLib_reent );
        }
        #endif /* configUSE_NEWLIB_REENTRANT */

        xNextTaskUnblockTime = portMAX_DELAY;

        /*
            本文注释：
            xSchedulerRunning 设置为 pdTRUE;
            表示调度器开始运行。
        */
        xSchedulerRunning = pdTRUE; 
        xTickCount = ( TickType_t ) 0U;

        /* If configGENERATE_RUN_TIME_STATS is defined then the following
        macro must be defined to configure the timer/counter used to generate
        the run time counter time base. */
        /*
            本文注释：
            configGENERATE_RUN_TIME_STATS 为 1 的时候使能时间统计功能。
            需要用户实现此宏，用来配置一个定时器/计数器。

            如：#define portCONFIGURE_TIMER_FOR_RUN_TIME_STATS()     (ulHighFrequencyTimerTicks = 0ul)
        */
        portCONFIGURE_TIMER_FOR_RUN_TIME_STATS();

        /* Setting up the timer tick is hardware specific and thus in the
        portable interface. */
        /*
            本文注释：
            调用函数 xPortStartScheduler() 来初始化跟调度器有关的硬件，
            比如滴答定时器、FPU 单元、PendSV 中断等。
        */
        if( xPortStartScheduler() != pdFALSE )
        {
            /* Should not reach here as if the scheduler is running the
            function will not return. */
            /*
                本文注释：如果调度器启动成功的话就不会运行到这里，函数不会有返回值。
            */
        }
        else
        {
            /* Should only reach here if a task calls xTaskEndScheduler(). */
            /*
                本文注释：不会运行到这里，除非调用函数 xTaskEndScheduler()
            */
        }
    }
    else
    {
        /* This line will only be reached if the kernel could not be started,
        because there was not enough FreeRTOS heap to create the idle task
        or the timer task. */
        /*
            本文注释：
            程序运行到这里只能说明一点，系统内核没有启动成功，
            原因是在创建空闲任务或者定时器任务的时候没有足够的内存。
        */
        configASSERT( xReturn != errCOULD_NOT_ALLOCATE_REQUIRED_MEMORY );
    }

    /* Prevent compiler warnings if INCLUDE_xTaskGetIdleTaskHandle is set to 0,
    meaning xIdleTaskHandle is not used anywhere else. */
    /*
        本文注释：
        防止编译器报错。
        比如宏 INCLUDE_xTaskCetIdleTaskHandle 定义为 0 的话，
        编译器就会提示 xIdleTaskHandle 未使用。
    */
    ( void ) xIdleTaskHandle;
}
</code></span></code></pre>
<h3 id="_17">内核相关硬件初始化函数</h3>
<pre><code class="pre-wrap"><span style="overflow-x: auto; max-width:100%; display:inline;"><code class="language-c">/* port.c */

BaseType_t xPortStartScheduler( void )
{
    /* configMAX_SYSCALL_INTERRUPT_PRIORITY must not be set to 0.
    See http://www.FreeRTOS.org/RTOS-Cortex-M3-M4.html */
    configASSERT( configMAX_SYSCALL_INTERRUPT_PRIORITY );

    /* This port can be used on all revisions of the Cortex-M7 core other than
    the r0p1 parts.  r0p1 parts should use the port from the
    /source/portable/GCC/ARM_CM7/r0p1 directory. */
    configASSERT( portCPUID != portCORTEX_M7_r0p1_ID );
    configASSERT( portCPUID != portCORTEX_M7_r0p0_ID );

    #if( configASSERT_DEFINED == 1 )
    {
        volatile uint32_t ulOriginalPriority;
        volatile uint8_t * const pucFirstUserPriorityRegister = ( uint8_t * ) ( portNVIC_IP_REGISTERS_OFFSET_16 + portFIRST_USER_INTERRUPT_NUMBER );
        volatile uint8_t ucMaxPriorityValue;

        /* Determine the maximum priority from which ISR safe FreeRTOS API
        functions can be called.  ISR safe functions are those that end in
        &quot;FromISR&quot;.  FreeRTOS maintains separate thread and ISR API functions to
        ensure interrupt entry is as fast and simple as possible.

        Save the interrupt priority value that is about to be clobbered. */
        ulOriginalPriority = *pucFirstUserPriorityRegister;

        /* Determine the number of priority bits available.  First write to all
        possible bits. */
        *pucFirstUserPriorityRegister = portMAX_8_BIT_VALUE;

        /* Read the value back to see how many bits stuck. */
        ucMaxPriorityValue = *pucFirstUserPriorityRegister;

        /* The kernel interrupt priority should be set to the lowest
        priority. */
        configASSERT( ucMaxPriorityValue == ( configKERNEL_INTERRUPT_PRIORITY &amp; ucMaxPriorityValue ) );

        /* Use the same mask on the maximum system call priority. */
        ucMaxSysCallPriority = configMAX_SYSCALL_INTERRUPT_PRIORITY &amp; ucMaxPriorityValue;

        /* Calculate the maximum acceptable priority group value for the number
        of bits read back. */
        ulMaxPRIGROUPValue = portMAX_PRIGROUP_BITS;
        while( ( ucMaxPriorityValue &amp; portTOP_BIT_OF_BYTE ) == portTOP_BIT_OF_BYTE )
        {
            ulMaxPRIGROUPValue--;
            ucMaxPriorityValue &lt;&lt;= ( uint8_t ) 0x01;
        }

        /* Shift the priority group value back to its position within the AIRCR
        register. */
        ulMaxPRIGROUPValue &lt;&lt;= portPRIGROUP_SHIFT;
        ulMaxPRIGROUPValue &amp;= portPRIORITY_GROUP_MASK;

        /* Restore the clobbered interrupt priority register to its original
        value. */
        *pucFirstUserPriorityRegister = ulOriginalPriority;
    }
    #endif /* conifgASSERT_DEFINED */

    /* Make PendSV and SysTick the lowest priority interrupts. */
    portNVIC_SYSPRI2_REG |= portNVIC_PENDSV_PRI;    /* 本文注释：设置 PendSV 的中断优先级，为最低优先级。 */
    portNVIC_SYSPRI2_REG |= portNVIC_SYSTICK_PRI;   /* 本文注释：设置 滴答定时器 的中断优先级，为最低优先级。 */

    /* Start the timer that generates the tick ISR.  Interrupts are disabled
    here already. */
    /* 本文注释：设置 滴答定时器 定时周期，并使能滴答定时器中断。 */
    vPortSetupTimerInterrupt(); 
    /* 
        //    /*
        //     * Setup the SysTick timer to generate the tick interrupts at the required
        //     * frequency.
        //     */
        //    #if configOVERRIDE_DEFAULT_TICK_CONFIGURATION == 0
        //
        //        void vPortSetupTimerInterrupt( void )
        //        {
        //            /* Calculate the constants required to configure the tick interrupt. */
        //            #if configUSE_TICKLESS_IDLE == 1
        //            {
        //                ulTimerCountsForOneTick = ( configSYSTICK_CLOCK_HZ / configTICK_RATE_HZ );
        //                xMaximumPossibleSuppressedTicks = portMAX_24_BIT_NUMBER / ulTimerCountsForOneTick;
        //                ulStoppedTimerCompensation = portMISSED_COUNTS_FACTOR / ( configCPU_CLOCK_HZ / configSYSTICK_CLOCK_HZ );
        //            }
        //            #endif /* configUSE_TICKLESS_IDLE */
        //
        //            /* Configure SysTick to interrupt at the requested rate. */
        //            portNVIC_SYSTICK_LOAD_REG = ( configSYSTICK_CLOCK_HZ / configTICK_RATE_HZ ) - 1UL;
        //            portNVIC_SYSTICK_CTRL_REG = ( portNVIC_SYSTICK_CLK_BIT | portNVIC_SYSTICK_INT_BIT | portNVIC_SYSTICK_ENABLE_BIT );
        //        }
        //
        //    #endif /* configOVERRIDE_DEFAULT_TICK_CONFIGURATION */

    */

    /* Initialise the critical nesting count ready for the first task. */
    /* 本文注释：初始化临界区嵌套计数器。 */
    uxCriticalNesting = 0;

    /* Ensure the VFP is enabled - it should be anyway. */
    /* 本文注释：使能 FPU */
    prvEnableVFP();
    /*
        //    __asm void prvEnableVFP( void )
        //    {
        //        PRESERVE8
        //
        //        /* The FPU enable bits are in the CPACR. */
        //        ldr.w r0, =0xE000ED88
        //        ldr   r1, [r0]
        //
        //        /* Enable CP10 and CP11 coprocessors, then save back. */
        //        orr   r1, r1, #( 0xf &lt;&lt; 20 )
        //        str r1, [r0]
        //        bx    r14
        //        nop
        //    }
    */

    /* Lazy save always. */
    /* 
        本文注释：
        设置 FPCCR 的 bit31 和 bit30 都为 1，这样 S0~S15 和 FPSCR 寄存器在异常入口和退出时会自动保存和恢复。
        并且异常流程使用 【惰性压栈】 的特性以保证中断等待。
    */
    *( portFPCCR ) |= portASPEN_AND_LSPEN_BITS;

    /* Start the first task. */
    /* 本文注释：开始第一个任务。 */
    prvStartFirstTask();
    /*
        //    __asm void prvStartFirstTask( void )
        //    {
        //        PRESERVE8
        //
        //        /* Use the NVIC offset register to locate the stack. */
        //        ldr r0, =0xE000ED08
        //        ldr r0, [r0]
        //        ldr r0, [r0]
        //        /* Set the msp back to the start of the stack. */
        //        msr msp, r0
        //        /* Globally enable interrupts. */
        //        cpsie i
        //        cpsie f
        //        dsb
        //        isb
        //        /* Call SVC to start the first task. */
        //        svc 0
        //        nop
        //        nop
        //    }

    */

    /* Should not get here! */
    /* 本文注释：正常代码执行是不会到这里的！！！ */
    return 0;
}
</code></span></code></pre>
<h4 id="fpu">开启 FPU 函数</h4>
<pre><code class="pre-wrap"><span style="overflow-x: auto; max-width:100%; display:inline;"><code class="language-c">/* port.c */

__asm void prvEnableVFP( void )
{
    PRESERVE8

    /* The FPU enable bits are in the CPACR. */
    ldr.w r0, =0xE000ED88           ;R0 = 0xE000ED88 (CPACR 寄存器的地址为 0xE000ED88)
    ldr r1, [r0]                    ;从 R0 的地址读取数据，赋值给 R1

    /* Enable CP10 and CP11 coprocessors, then save back. */
    orr r1, r1, #( 0xf &lt;&lt; 20 )      ;R1 = R1|(0xf &lt;&lt; 20)
    str r1, [r0]                    ;R1 中的值，写入到 R0 保存的地址中
    bx  r14
    nop
}
</code></span></code></pre>
<h4 id="_18">启动第一个任务</h4>
<pre><code class="pre-wrap"><span style="overflow-x: auto; max-width:100%; display:inline;"><code class="language-c">/* port.c */

__asm void prvStartFirstTask( void )
{
    PRESERVE8

    /* Use the NVIC offset register to locate the stack. */
    /* 
        一般来说向量表应该从起始地址 0x00000000 开始存储的，
        不过有些应用可能需要在运行时修改或重定义向量表，
        Cortex-M 处理器为此提供了一个叫做向量表重定位的特性。

        向量表重定位特性提供了一个名为向量表偏移寄存器（VTOR）的可编程寄存器。
        通过这个寄存器可以重定义向量表。
        比如 STM32F407 的 ST 官方库会通过函数 SystemInit() 来设置 VTOR 寄存器。
        SCB-&gt;VTOR = FLASH_BASE|VECT_TAB_OFFSET;     // VTOR = 0x08000000+0x00
    */
    ldr r0, =0xE000ED08             ;R0 = 0xE000ED08（VTOR 寄存器的地址为 0xE000ED08）
    ldr r0, [r0]                    ;从 R0 的地址读取数据，赋值给 R0（此语句执行完之后，R0 中的值应该为 0x08000000）
    ldr r0, [r0]                    ;获取 MSP 初始值（向量表的起始地址保存的就是主栈指针 MSP 的初始值）
    /* Set the msp back to the start of the stack. */
    msr msp, r0                     ;复位 MSP（R0 保存了 MSP 的初始值，将其赋值给 MSP，相当于复位 MSP）
    /* Globally enable interrupts. */
    cpsie i                         ;使能中断（清除 PRIMASK）
    cpsie f                         ;使能中断（清除 FAULTMASK）
    dsb                             ;数据同步屏障
    isb                             ;指令同步屏障
    /* Call SVC to start the first task. */
    /* FreeRTOS 仅使用 SVC 异常来启动第一个任务，后面不再使用 SVC 了 */
    svc 0                           ;触发 SVC 中断（异常，请求管理调用） 
    nop
    nop
}
</code></span></code></pre>
<h4 id="svc">SVC 中断服务函数</h4>
<p><span style="overflow-x: auto; max-width:100%; display:inline;"><code>prvStartFirstTask()</code></span> 通过调用 SVC 指令触发了 SVC 中断，而第一个任务的启动就是在 SVC 中断服务函数中完成的。</p>
<pre><code class="pre-wrap"><span style="overflow-x: auto; max-width:100%; display:inline;"><code class="language-c">/* port.c */

__asm void vPortSVCHandler( void )
{
    PRESERVE8

    /* Get the location of the current TCB. */
    /*  
        本文注释：pxCurrentTCB 是一个 TCB_t 类型的指针，这个指针永远指向正在运行的任务。
        PRIVILEGED_DATA TCB_t * volatile pxCurrentTCB = NULL; 
    */
    ldr r3, =pxCurrentTCB           ;R3 = pxCurrentTCB 的地址
    ldr r1, [r3]                    ;从 R3 的地址读取数据，赋值给 R1 // 本文注释：获取当前任务的任务控制块的存储地址。
    ldr r0, [r1]                    ;从 R1 的地址读取数据，赋值给 R0
    /*  
        本文注释：任务控制块的第一个字段就是任务堆栈的栈顶指针 pxTopOfStack 所指向的位置，
        读取任务控制块的首地址，就能得到栈顶指针所指向的地址。

        这三条语句的目的是获取要切换到这个任务的任务栈顶指针，
        任务所对应的的寄存器的值（即现场）都保存在任务的任务堆栈中，
        因此需要获取栈顶指针来恢复这些寄存器值。
    */
    /* Pop the core registers. */
                                                        /* 本文注释：ldmia 是多加载/存储指令。 */
    ldmia r0!, {r4-r11, r14}        ;出栈，R4~R11 和 R14 /* 本文注释：此语句从任务堆栈中将 R4~R11 和 R14 的值恢复了 */
    msr psp, r0                     ;进程栈指针 PSP 设置为任务的堆栈
    isb                             ;指令同步屏障
    mov r0, #0                      ;R0 = 0
    msr basepri, r0                 ;寄存器 basepri = 0，开启中断
    bx r14
    /* R0~R3, R12, PC, xPSR 会在退出中断的时候由 MCU 自动出栈（恢复） */
}
</code></span></code></pre>
<p>ldmia 指令</p>
<pre><code class="pre-wrap"><span style="overflow-x: auto; max-width:100%; display:inline;"><code class="language-c">ldmia Rn!, {reg list} 
</code></span></code></pre>
<p>表示从 Rn 指定的存储器位置读取多个字，地址在每次读取后增加，Rn 在传输完成以后写回。</p>
<p>示例：</p>
<pre><code class="pre-wrap"><span style="overflow-x: auto; max-width:100%; display:inline;"><code class="language-c">ldr     R0, =0x800
ldmia   R0!, {R2~R4}
/*
    给 R0 赋值  0x800
    从地址 0x800 读出数据，赋值为 R2
    从地址 0x804 读出数据，赋值为 R3
    从地址 0x808 读出数据，赋值为 R4
    最后把地址写回 R0（R0的值为 0x80A）
*/
</code></span></code></pre>
  </div>
</div>
 
    </div>
  </div>
  <div id="footer-background">
    <div id="footer">
      <div class="legal pure-g">
  <div class="pure-u-1 u-sm-1-2">
    <p class="legal-license"><a href="https://beian.miit.gov.cn/#/Integrated/index">浙ICP备2020038748号</a></p>
  </div>
  <div class="pure-u-1 u-sm-1-2">
    <p class="legal-links"><a href="https://github.com/zromyk">GitHub</a></p>
    <p class="legal-copyright">Copyright © 2021 Wei Zhou. 保留所有权利。</p>
  </div>
</div>
    </div>
  </div>
  <!-- <script src="https://cdn.bootcss.com/jquery/3.2.1/jquery.min.js"></script> -->
  <script src="/style/html/jquery.min.js"></script>
  <script src='/style/article/latex/latest.js?config=TeX-MML-AM_CHTML'></script>
<!-- <script src="https://cdn.geogebra.org/apps/deployggb.js"></script> -->
<script src="/style/article/deployggb.js"></script>
<!-- <script src="https://apps.bdimg.com/libs/highlight.js/9.1.0/highlight.min.js"></script> -->
<script type="text/javascript">
  // 脚本：navigation 随鼠标移动自动变换宽度
  var element = document.getElementById("navigation"); // 获取要操作的元素
  var elementWidth = parseInt(getComputedStyle(element).width);
  var elementLeft = 0;
  var elementRight = 0;
  element.addEventListener('mouseenter', function (event) { // 添加鼠标按下事件的监听器
    elementLeft = element.getBoundingClientRect().left - 10;
    elementRight = element.getBoundingClientRect().left + elementWidth * 3;
    window.addEventListener('mousemove', resize); // 添加全局的鼠标移动事件的监听器
  });

  function resize(event) {
    var minWidth = elementWidth;
    var maxWidth = elementWidth * 2.5;
    // console.log(elementLeft, event.clientX, elementRight, event.clientX - elementLeft + elementWidth / 2);
    if (elementLeft <= event.clientX && event.clientX <= elementRight) {
      var width = event.clientX - elementLeft + elementWidth / 2;
      width = Math.min(width, maxWidth);
      width = Math.max(width, minWidth);
      element.style.width = width + 'px'; // 设置新的宽度样式属性
    }
    else {
      element.style.width = elementWidth + 'px'; // 设置新的宽度样式属性
      stopResize();
    }
  }

  function stopResize() {
    element.style.width = elementWidth + 'px'; // 设置新的宽度样式属性
    // console.log("stopResize", elementLeft, event.clientX, elementRight, event.clientX - elementLeft + elementWidth / 2);
    window.removeEventListener('mousemove', resize); // 移除鼠标移动事件的监听器
  }
</script>
<script src="/style/article/highlight/highlight.min.js"></script>
<script type="text/javascript">
  // 脚本：code语法高亮
  hljs.initHighlightingOnLoad();
</script>
<script>
  function animateByNav() {
    $("html").animate({
        scrollTop: ($(event.target.hash).offset().top - 52)
    }, 300);
  };
</script>
<script src="/style/article/pell-1.0.6/dist/pell.js"></script>
<script>
  // 脚本：自由编辑页面
  var editor = window.pell.init({
    element: document.getElementById('editor'),
    defaultParagraphSeparator: 'p',
    onChange: function(html) {
        document.getElementById('text-output').innerHTML = html
        document.getElementById('html-output').textContent = html
    }
  });

  function markdownEditor() {
    var articles = document.getElementById('content-articles-markdown');
    if (articles.getAttribute("contenteditable") == "true") {
        articles.setAttribute("contenteditable", "false");
        document.getElementById("content-articles-markdownEditor").style.display = "none"; //隐藏
        document.getElementById("button-markdownEditor").innerHTML = "启用编辑";
    } else {
        articles.setAttribute("contenteditable", "true");
        document.getElementById("content-articles-markdownEditor").style.display = ""; //显示
        document.getElementById("button-markdownEditor").innerHTML = "关闭编辑";
    }
  };

  function save() {
      window.alert("保存成功");
  };
</script>

</body>
</html>
